/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Simulation.Services.Common;

import Simulation.Services.Common.Dis.*;
import Simulation.Utils.CoordinateConversions;
import java.util.LinkedList;
import javax.swing.Timer;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
 *
 * @author CATT
 */
public class EntityDatabase 
{
    public interface Command{
        public void execute(Object data);
    }
    
    private int Entity_Database_Max_Size = 5000;
    private int Entity_Database_Entity_Timeout_Secs = 15;
    private LinkedList<EntityDbStorageRecord> EntityDb = new LinkedList<EntityDbStorageRecord>();
    private Command SendEntityPduCmd;
    
    private ActionListener LocalEntBCastUpdateListener = new ActionListener() {
      @Override 
      public void actionPerformed(ActionEvent actionEvent) {
        //Send local entity state PDU's
        for (int Ent = 0; Ent < EntityDb.size();Ent++){
            if (((EntityDb.get(Ent).EntRecord.IsLocal))&&(EntityDb.get(Ent).EntRecord.IsActive)){
                
                EntityRecord currentEnt = EntityDb.get(Ent).EntRecord;
                
                //Send the entity pdu
                SendEntityPduCmd.execute(currentEnt);
                
                //System.out.println("Entity Database Broadcasting Entity =  " + EntityDb.get(Ent).EntRecord.EntityID.Site + ":" + 
                //                   EntityDb.get(Ent).EntRecord.EntityID.application + ":" + EntityDb.get(Ent).EntRecord.EntityID.entity);
            }            
        } 
      }
    };

    private ActionListener RemoteEntTimeoutListener = new ActionListener() {
      @Override 
      public void actionPerformed(ActionEvent actionEvent) {
        //Send local entity state PDU's
        for (int Ent = EntityDb.size(); Ent > 0 ;Ent--){
            if ((!EntityDb.get(Ent-1).EntRecord.IsLocal)){
                EntityDb.get(Ent-1).SecondsSinceLastUpdate++;
                //Check to see if the time out period has elapsed
                if(EntityDb.get(Ent-1).SecondsSinceLastUpdate>Entity_Database_Entity_Timeout_Secs)
                {
                    try{
                        //System.out.println("Entity Database Timed Out Entity =   " + EntityDb.get(Ent-1).EntRecord.EntityID.Site + ":" + 
                        //           EntityDb.get(Ent-1).EntRecord.EntityID.application + ":" + EntityDb.get(Ent-1).EntRecord.EntityID.entity);
                        RemoveEntity(EntityDb.get(Ent-1).EntRecord.EntityID);
                    }
                    catch(Exception e){
                        System.out.println("ERROR> RemoteEntTimeoutListener : " + e.getMessage());                        
                    }
                }
            }            
        } 
      }
    };
    
    private Timer RemoteEntityTimeout = new Timer(1000, RemoteEntTimeoutListener);
    private Timer LocalEntBCastUpdate = new Timer(2000, LocalEntBCastUpdateListener);
    
    public void EnablePduSend(Command cmd)
    {
        SendEntityPduCmd = cmd;
        LocalEntBCastUpdate.setDelay(2000);
        LocalEntBCastUpdate.start();
    }
    
    public class EntityId{
        public int Site = 0;
        public int application = 0;
        public int entity = 0;
    }
    
    public class EntityAppearance{
        public int Specific;
        public int General;
    }

    public class EntityRecord{
        public EntityId EntityID;
        public int ExerciseId = 0;
        public int Force = 0;
        public boolean IsLocal = false;
        public boolean IsActive = false;
        public int Type = 0;
        public Vector3Double Location;
        public Vector3Double Orientation;
        public EntityAppearance Appearance;
        public Vector3Double Velocity;
        public String Marking = "";
        public boolean EntityRemove = false;
    }    
    
    private class EntityDbStorageRecord
    {
        EntityRecord EntRecord = new EntityRecord();
        int SecondsSinceLastUpdate = 0;
    }

    public int GetEntityCount(){
        return EntityDb.size();
    }
    
    public int GetLocalEntityCount(){
        int count = 0;
        for (int Ent = EntityDb.size(); Ent > 0 ;Ent--){
            if ((EntityDb.get(Ent-1).EntRecord.IsLocal)){
                count++;
            }
        }
        return count;
    }
    
    public int GetRemoteEntityCount(){
        int count = 0;
        for (int Ent = EntityDb.size(); Ent > 0 ;Ent--){
            if ((!EntityDb.get(Ent-1).EntRecord.IsLocal)){
                count++;
            }
        }
        return count;
    }
    
    public void CreateRemoteEntity(EntityStatePdu ent)  
    {
        EntityRecord newEntity = new EntityRecord();
        //Id
        newEntity.EntityID = new EntityId();
        newEntity.EntityID.Site = ent.getEntityID().getSite();
        newEntity.EntityID.application = ent.getEntityID().getApplication();
        newEntity.EntityID.entity = ent.getEntityID().getEntity();
        
        newEntity.EntityRemove = false;
        newEntity.ExerciseId = ent.getExerciseID();
        newEntity.Force = ent.getForceId();
        newEntity.IsActive = true;
        newEntity.IsLocal = false;
        
        //Location
        newEntity.Location = new Vector3Double();
        double[] location = new double[3];
        location[0] = ent.getEntityLocation().getX();
        location[1] = ent.getEntityLocation().getY();
        location[2] = ent.getEntityLocation().getZ();
        double[] convertedLoc = CoordinateConversions.xyzToLatLonDegrees(location);

        newEntity.Location.setX(convertedLoc[0]);
        newEntity.Location.setY(convertedLoc[1]);
        newEntity.Location.setZ(convertedLoc[2]);
        
        //Orientation
        Vector3Double orientation = new Vector3Double();
        orientation.setX(ent.getEntityOrientation().getTheta());
        orientation.setY(ent.getEntityOrientation().getPhi());
        orientation.setZ(ent.getEntityOrientation().getPsi());
        newEntity.Orientation = CoordinateConversions.getOrientationfromDisOrientation(orientation);

        //Entity type
        newEntity.Type = 0;
        
        //Marking
        newEntity.Marking = "N/A       ";
                
        //Velocity
        newEntity.Velocity = new Vector3Double();
        newEntity.Velocity.setX(ent.getEntityLinearVelocity().getX());
        newEntity.Velocity.setY(ent.getEntityLinearVelocity().getY());
        newEntity.Velocity.setZ(ent.getEntityLinearVelocity().getZ());
        
        if (!UpdateEntityIfInDatabase(newEntity)) {
            boolean created = CreateEntity(newEntity);
           // System.out.println("We Have Added " + newEntity.EntityID.Site + ":"
           //                                     + newEntity.EntityID.application + ":"
           //                                     + newEntity.EntityID.entity + " To The Database!");
        }
    }

    //Returns true if the entity is present in the entity database
    public synchronized boolean UpdateEntityIfInDatabase(EntityRecord EntRec){
        EntityId EntId = EntRec.EntityID;
        
        //Go through the database and see if we can find it
        for (int Ent = 0; Ent < EntityDb.size();Ent++){
            int site = EntityDb.get(Ent).EntRecord.EntityID.Site;
            int application = EntityDb.get(Ent).EntRecord.EntityID.application;
            int entity = EntityDb.get(Ent).EntRecord.EntityID.entity;
            
            //Check to see if it is the current entity
            if ((site == EntId.Site)&&(application == EntId.application)&&(entity == EntId.entity)){
                //Found it so update it and exit
                EntityDbStorageRecord temp = EntityDb.get(Ent);
                temp.SecondsSinceLastUpdate = 0;
                temp.EntRecord = EntRec;
                EntityDb.set(Ent, temp);
                
                return true;    
            }            
        }
        return false;
    }

    //Returns true if the entity is present in the entity database
    public boolean EntityInDatabase(EntityId EntId){
        for (int Ent = 0; Ent < EntityDb.size();Ent++){
            int site = EntityDb.get(Ent).EntRecord.EntityID.Site;
            int application = EntityDb.get(Ent).EntRecord.EntityID.application;
            int entity = EntityDb.get(Ent).EntRecord.EntityID.entity;
            
            if ((site == EntId.Site)&&(application == EntId.application)&&(entity == EntId.entity)){
                return true;    
            }            
        }
        return false;
    }
    
    //Returns the entity record for the entity or null if it is not present in the entity database.
    //NOTE: it is recommended that this is not called without first checking the entity is present
    //within the database.
    public EntityRecord GetEntityRecord(EntityId EntId){        
        for (int Ent = 0; Ent < EntityDb.size();Ent++){
            if (EntityDb.get(Ent).EntRecord.EntityID.equals(EntId)){
                return EntityDb.get(Ent).EntRecord;    
            }            
        }
        return null;
    }

    //Removes an entity from the entity database so long as its locally created.
    //Note that this will also send out an entity remove PDU.
    public synchronized void RemoveEntity(EntityId EntId){
        for (int Ent = 0; Ent < EntityDb.size();Ent++){
            if ((EntityDb.get(Ent).EntRecord.EntityID.equals(EntId))){
                EntityDb.get(Ent).EntRecord.EntityRemove = true;    
            }            
        } 
    }
    
    //Create an entity in the entity database
    public boolean CreateEntity(EntityRecord Ent){
        try{
            //Check to see if the entity database is full
            if(EntityDb.size()<=Entity_Database_Max_Size){
                //Make sure the entity does not already exist in the database
                if (!EntityInDatabase(Ent.EntityID)){
                    //Create a new storage record and store the entity
                    EntityDbStorageRecord TempRec = new EntityDbStorageRecord();
                    TempRec.EntRecord = Ent;
                    TempRec.SecondsSinceLastUpdate = 0;
                    //Add the entity
                    EntityDb.add(TempRec);  
                    return true;
                }
                else{
                    System.out.println("ERROR - CreateEntity : Entity Already In Database!");
                    return false;   
                }
            }
            else{
                System.out.println("ERROR - CreateEntity : EntityDB Full!");
                return false;
            }
        }
        catch(Exception e){
            System.out.println("ERROR - CreateEntity : " + e.getMessage());
            return false;
        }
    }

    //Returns the entity's type
    public int GetType(EntityId EntId){
        for (int Ent = 0; Ent < EntityDb.size();Ent++){
            if (EntityDb.get(Ent).EntRecord.EntityID.equals(EntId)){
                return EntityDb.get(Ent).EntRecord.Type;    
            }            
        } 
        return 0;
    }

    //Returns the entity's force
    public int GetEntityForce(EntityId EntId){
        for (int Ent = 0; Ent < EntityDb.size();Ent++){
            if (EntityDb.get(Ent).EntRecord.EntityID.equals(EntId)){
                return EntityDb.get(Ent).EntRecord.Force;    
            }            
        } 
        return 0;
    }
    
    //Returns the entity's location
    public Vector3Double GetEntityLocation(EntityId EntId){
        for (int Ent = 0; Ent < EntityDb.size();Ent++){
            if (EntityDb.get(Ent).EntRecord.EntityID.equals(EntId)){
                return EntityDb.get(Ent).EntRecord.Location;    
            }            
        } 
        return null;
    }

    //Returns the entity's orientation
    public Vector3Double GetEntityOrientation(EntityId EntId){
        for (int Ent = 0; Ent < EntityDb.size();Ent++){
            if (EntityDb.get(Ent).EntRecord.EntityID.equals(EntId)){
                return EntityDb.get(Ent).EntRecord.Orientation;    
            }            
        } 
        return null;
    }
    
    //Returns the entity's velocity
    public Vector3Double GetEntityVelocity(EntityId EntId){
        for (int Ent = 0; Ent < EntityDb.size();Ent++){
            if (EntityDb.get(Ent).EntRecord.EntityID.equals(EntId)){
                return EntityDb.get(Ent).EntRecord.Velocity;    
            }            
        } 
        return null;
    }
    
    //Returns the entity's exercise Id
    public int GetEntityExerciseId(EntityId EntId){
        for (int Ent = 0; Ent < EntityDb.size();Ent++){
            if (EntityDb.get(Ent).EntRecord.EntityID.equals(EntId)){
                return EntityDb.get(Ent).EntRecord.ExerciseId;    
            }            
        } 
        return 0;
    }
    
    //Activates the entity if it is locally created and sends
    //it's entity status out on the simulation network.
    public void ActivateEntity(EntityId EntId){
        for (int Ent = 0; Ent < EntityDb.size();Ent++){
            if ((EntityDb.get(Ent).EntRecord.EntityID.equals(EntId))&&(EntityDb.get(Ent).EntRecord.IsLocal)){
                EntityDb.get(Ent).EntRecord.IsActive = true;    
            }            
        }
    }

    //Deactivates the entity if it is locally created and stops
    //sending it's entity status out on the simulation network.
    public void DeactivateEntity(EntityId EntId){
        for (int Ent = 0; Ent < EntityDb.size();Ent++){
            if ((EntityDb.get(Ent).EntRecord.EntityID.equals(EntId))&&(EntityDb.get(Ent).EntRecord.IsLocal)){
                EntityDb.get(Ent).EntRecord.IsActive = false;    
            }            
        } 
    }
    
    //Returns true if the entity was created locally
    public boolean IsLocal(EntityId EntId){
        for (int Ent = 0; Ent < EntityDb.size();Ent++){
            if (EntityDb.get(Ent).EntRecord.EntityID.equals(EntId)){
                if (EntityDb.get(Ent).EntRecord.IsLocal){
                    return true;    
                }
                else{
                    return false;
                }
            }            
        } 
        return false;
    }

    public void PrintEntityDatabase()
    {
        System.out.println("");
        System.out.println("");
        System.out.println("@@@@@@@@@@@@@@@@@@@ ENTITY DATABASE @@@@@@@@@@@@@@@@@@@@@@");
        System.out.println("");
        for (int Ent = 0; Ent < EntityDb.size();Ent++){
            
            EntityDbStorageRecord Recrd = EntityDb.get(Ent);
            System.out.println("---------------- Entity " + Ent + "-----------------------");
            System.out.println("Entity Id - " + Recrd.EntRecord.EntityID.Site + ":" + 
                                   Recrd.EntRecord.EntityID.application + ":" + Recrd.EntRecord.EntityID.entity);
            System.out.println("Entity Marking - " + Recrd.EntRecord.Marking);
            System.out.println("Entity Local - " + Recrd.EntRecord.IsLocal);
            System.out.println("Entity Type - " + Recrd.EntRecord.Type);
            System.out.println("Entity Force - " + Recrd.EntRecord.Force);
            System.out.println("Entity Location -  x: " + Recrd.EntRecord.Location.getX() + "    y: " + Recrd.EntRecord.Location.getY() + "    z: " + Recrd.EntRecord.Location.getZ());
            System.out.println("Entity Orientation - x: " + Recrd.EntRecord.Orientation.getX() + "    y: " + Recrd.EntRecord.Orientation.getY() + "    z: " + Recrd.EntRecord.Orientation.getZ());
            System.out.println("Entity Velocity -  x: " + Recrd.EntRecord.Velocity.getX() + "    y: " + Recrd.EntRecord.Velocity.getY() + "    z: " + Recrd.EntRecord.Velocity.getZ());
        }
    }

    public void UpdateDatabaseThreasholds(int maxSize, int timeoutRate)
    {
        Entity_Database_Max_Size = maxSize;
        Entity_Database_Entity_Timeout_Secs = timeoutRate;
    }
    
    public EntityDatabase()
    {
        RemoteEntityTimeout.setDelay(1000);
        RemoteEntityTimeout.start();
    }
}